#include "UsbHost.h"
#include "uotghs_otg.h"
#include "uotghs_host.h"
#include "sysclk.h"

static UsbHost g_UsbHost__;
UsbHost* g_pUsbHost = &g_UsbHost__;

#define SETUP_PIPE_NUM 0
#define BLKIN_PIPE_NUM 1
#define BLKOUT_PIPE_NUM 2

typedef void (*uhd_callback_reset_t)(void);
uhd_callback_reset_t uhd_reset_callback = NULL;

static Pipe* UsbHost_GetPipe(uint8 pipeNum)
{
	int i = 0;
	for(i = 0; i < MAX_PIPE_COUNT; i++)
	{
		if(g_pUsbHost->m_pPipe[i] && pipeNum == g_pUsbHost->m_pPipe[i]->m_PipeNum)
		{
			return g_pUsbHost->m_pPipe[i];
		}
	}

	return Null;
}

void UsbHost_BusReset(void)
{
}


static void UsbHost_IsrBusReset(void)
{
    //Clear reset interrupt flag
	UOTGHS->UOTGHS_HSTICR = UOTGHS_HSTICR_RSTIC; //uhd_ack_reset_sent();
	if (uhd_reset_callback != NULL) 
	{
		uhd_reset_callback();
	}
	return;
}

static void UsbHost_disable(bool b_id_stop)
{
	UNUSED(b_id_stop);
	irqflags_t flags;

	flags = cpu_irq_save();  
	// Check USB clock ready after a potential sleep mode < IDLE
	while (!Is_otg_clock_usable());
	Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_FRZCLK);

	// Disable Vbus change and error interrupts
	Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_VBUSTE | UOTGHS_CTRL_VBERRE);

	// Disable main control interrupt
	// (Connection, disconnection, SOF and reset)
	UOTGHS->UOTGHS_HSTIDR = UOTGHS_HSTIDR_DCONNIEC | UOTGHS_HSTIDR_DDISCIEC
		| UOTGHS_HSTIDR_RSTIEC   | UOTGHS_HSTIDR_RSMEDIEC
		| UOTGHS_HSTIDR_RXRSMIEC | UOTGHS_HSTIDR_HSOFIEC
		| UOTGHS_HSTIDR_HWUPIEC;

	Clr_bits(UOTGHS->UOTGHS_HSTCTRL, UOTGHS_HSTCTRL_SOFE);                  

	// Unalloc pipe
	uhd_disable_pipe(0);
	uhd_unallocate_memory(0);

	// Stop transfer on this pipe
	// Endpoint interrupt, bulk or isochronous
	Set_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_FRZCLK);

	otg_disable();
	otg_disable_pad();
	sysclk_disable_usb();
	pmc_disable_periph_clk(ID_UOTGHS);

	// Do not authorize asynchronous USB interrupts
	/*PMC->PMC_FSMR &= ~(PMC_FSMR_USBAL & PMC_FAST_STARTUP_Msk);
	Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_USBE);
	PMC->CKGR_UCKR &= ~CKGR_UCKR_UPLLEN;

	if ((PMC->PMC_PCSR1 & (1u << (ID_UOTGHS - 32))) == (1u << (ID_UOTGHS - 32))) 
	{
	PMC->PMC_PCDR1 = 1 << (ID_UOTGHS - 32);
	}*/
	cpu_irq_restore(flags);
}

static void UsbHost_IsrDisConnect(void)
{
	uhd_ack_disconnection();
	uhd_disable_disconnection_int();
	// Stop reset signal, in case of disconnection during reset
	uhd_stop_reset();
            
    //uhd_disable_reset_sent_interrupt();
    //uhd_start_reset();
    UsbHost_disable(false);
    ex_status.usb_role = USB_ROLE_SWITCH;
    //b_usb_switch = true;                    
    //update_jpm_status(JPM_AS_DEVICE);
            
	// Disable wakeup/resumes interrupts,
	// in case of disconnection during suspend mode
	/*UOTGHS->UOTGHS_HSTIDR = UOTGHS_HSTIDR_HWUPIEC
			| UOTGHS_HSTIDR_RSMEDIEC
			| UOTGHS_HSTIDR_RXRSMIEC;
	uhd_sleep_mode(UHD_STATE_DISCONNECT);
	uhd_ack_connection();
	uhd_enable_connection_int();
	uhd_suspend_start = 0;
	uhd_resume_start = 0;
	uhc_notify_connection(false);*/
	return;
}

static void UsbHost_IsrConnect()
{
    //Clear connection interrupt flag
	UOTGHS->UOTGHS_HSTICR = UOTGHS_HSTICR_DCONNIC; //uhd_ack_connection();                
    //disable connection intr
    UOTGHS->UOTGHS_HSTIDR = UOTGHS_HSTIDR_DCONNIEC;//uhd_disable_connection_int();
	//uhd_ack_disconnection();
	//uhd_enable_disconnection_int();  //not to care disconnect, %by Santiago
	
    //Start send SOF
    //uhd_enable_sof();
    Set_bits(UOTGHS->UOTGHS_HSTCTRL, UOTGHS_HSTCTRL_SOFE);
            
    // not to use any about suspend\sleep, %by Santiago
	//uhd_sleep_mode(UHD_STATE_IDLE);
	//uhd_suspend_start = 0;
	//uhd_resume_start = 0;
	
    //uhc_notify_connection(true);
    //uhc_connection_tree(true, &g_uhc_device_root);
    
    //uhc_enum_try = 1;
	//g_uhc_device_root.conf_desc = NULL;
	//g_uhc_device_root.address = 0;
	
	//UHC_CONNECTION_EVENT(uhc_dev_enum, true);
	//UsbHost_BusReset();
}

static void UsbHost_IsrVBusErr()
{
	//uhd_ack_vbus_error_interrupt();
}

//VBus Transition
static void UsbHost_IsrVBusTransition()
{
	// Check USB clock ready after asynchronous interrupt
	while (!Is_otg_clock_usable());
	//otg_unfreeze_clock();
	Clr_bits(UOTGHS->UOTGHS_CTRL, UOTGHS_CTRL_FRZCLK);

	otg_ack_vbus_transition();
}

/**
 * \internal
 * \brief Function called by UOTGHS interrupt to manage USB host interrupts
 *
 * USB host interrupt events are split into four sections:
 * - USB line events
 *   (VBus error, device dis/connection, SOF, reset, suspend, resume, wakeup)
 * - control endpoint events
 *   (setup reception, end of data transfer, underflow, overflow, stall, error)
 * - bulk/interrupt/isochronous endpoints events
 *   (end of data transfer, stall, error)
 */
void usb_host_interrupt(void)
{
	int i = 0;
	uint32_t status = UOTGHS->UOTGHS_HSTISR;
	uint32_t mask = UOTGHS->UOTGHS_HSTIMR;
	uint32 pipeIntMask = 0x3FF;
	
	uint32 pipeNum;
	Pipe* pPipe = Null;

	// Manage SOF interrupt
	if (Is_uhd_sof()) 
	{
		uhd_sof_interrupt();
		return;
	}

	// Is pipe interrupt?
	pipeNum = ((status & mask) >> 8);
	if(pipeNum & pipeIntMask)
	{
		for(i = 0; i < 10; i++, pipeNum >>= 1)
		{
			if(pipeNum & 0x01)
			{
				break;
			}
		}

		pipeNum = i;
		pPipe = UsbHost_GetPipe(pipeNum);
		Assert(pPipe);
		if(pPipe && pPipe->Isr)
		{
			pPipe->Isr(pPipe);
		}
		return;
	}
	// USB bus reset detection
	if (Is_uhd_reset_sent()) 
    {
		UsbHost_IsrBusReset();
	}

	if(Is_uhd_disconnection() && Is_uhd_disconnection_int_enabled())
	{
		UsbHost_IsrDisConnect();
	}
	
	if (Is_uhd_connection() && Is_uhd_connection_int_enabled()) 
	{
		UsbHost_IsrConnect();
	}

	if (Is_uhd_vbus_error_interrupt()) 
	{
		UsbHost_IsrVBusErr();
		return;
	}	

	// Manage Vbus state change
	if (Is_otg_vbus_transition()) 
	{
		UsbHost_IsrVBusTransition();
		return;
	}
}

void UsbHost_RemovePipe(Pipe* pPipe)
{
	int i = 0;
	
	for(i = 0; i < MAX_PIPE_COUNT; i++)
	{
		if(pPipe == g_pUsbHost->m_pPipe[i])
		{
			g_pUsbHost->m_pPipe[i] = Null;
			return;
		}
	}
}

void UsbHost_AddPipe(Pipe* pPipe)
{
	int i = 0;
	
	for(i = 0; i < MAX_PIPE_COUNT; i++)
	{
		if(Null == g_pUsbHost->m_pPipe[i])
		{
			g_pUsbHost->m_pPipe[i] = pPipe;
			return;
		}
	}

	Assert(False);
}

void UsbHost_Init()
{
	memset(g_pUsbHost, 0, sizeof(UsbHost));
	
}


